Spring MVC 学习笔记

Spring MVC简介

Spring MVC是Spring框架中用于Web应用快速开发的一个模块,其中的MVCModel-View-Controller的缩写。它是一个广泛应用于图形化用户交互开发中的设计模式,不仅常见于Web开发,也广泛应用于如SwingJavaFX等桌面开发。作为当今业界最主流的Web开发框架,Spring MVC已经成为当前最热门的开发技能。

IDEA环境配置

https://www.cnblogs.com/zuti666/p/13987082.html

核心概念

  • IoC(Inversion of Control)控制反转

    • 使用对象时,由主动new产生对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转
  • Spring技术对IoC思想进行了实现

    • Spring提供了一个容器,成为IoC容器,用来充当Ioc思想中的外部
    • IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
image-20220503134511431
  • DI(Dependency Injection)依赖注入
    • 当需要使用service对象的时候,同样需要用到dao对象,所以在IoC容器中做了一个依赖绑定。
    • 在容器中建立bean与bean之间的依赖关系的整个过程,成为依赖注入
image-20220503134958535
  • 目标: 充分解耦
    • 使用IoC容器管理 bean(IoC)
    • 在IoC容器内将有依赖关系的bean进行关系绑定(DI)
  • 最终效果
    • 使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系

IoC入门案例

IoC入门案例思路分析

  1. 管理什么?(Service与Dao)
  2. 如何将被管理的对象告知IoC容器?(配置)
  3. 被管理的对象交给IoC容器,如何获取到IoC容器?(接口)
  4. IoC容器得到后,如何从容器中获取bean?(接口方法)
  5. 使用Spring导入哪些坐标?(pom.xml)

案例1

BookDaoImpl

public class BookDaoImpl implements BookDao {
    public void save(){
        System.out.println("book dao save ...");
    }
}

BookServiceImpl

public class BookServiceImpl implements BookService {

    private BookDao bookDao = new BookDaoImpl();

    public void save(){
        System.out.println("book service save ...");
        bookDao.save();
    }
}

BookDao接口

public interface BookDao {
    void save();
}

BookService接口

public interface BookService {
    void save();
}

App1

public class app {
    public static void main(String[] args) {
        BookService bookService = new BookServiceImpl();
        bookService.save();
    }
}
/*运行结果
book service save ...
book dao save ...
*/

案例2

配置Spring

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--    1.导入spring的坐标spring-context-->

<!--    2、配置bean-->
<!--    bean标签表示配置bean
        id属性表述给bean起名字
        class属性表示给bean定义类型-->
    <bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
    <bean id="bookService" class="com.le1a.service.impl.BookServiceImpl"/>
</beans>

app2

package com.le1a;

import com.le1a.dao.BookDao;
import com.le1a.service.BookService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class app2 {
    public static void main(String[] args) {
        //3、获取IoC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //4、获取bean
//        BookDao bookDao = (BookDao) ctx.getBean("bookDap");
//        bookDao.save();

       //因为IoC容器对service和bean做了绑定,所以无需自己获取bean
       BookService bookService = (BookService) ctx.getBean("bookService");
       bookService.save();
    }
}

总结

1、导入Spring坐标

<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-context</artifactId>
  <version>${spring.version}</version>
</dependency>

2、定义Spring管理的类(接口)

public interface BookService{
    public void save();
}
public class BookServiceImpl implements BookService {

    private BookDao bookDao = new BookDaoImpl();

    public void save(){
        bookDao.save();
    }
}

3、创建Spring配置文件,配置对应类作为Spring管理的bean

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="bookService" class="com.le1a.service.impl.BookServiceImpl"/>
</beans>

注意!bean定义时id属性在同一个上下文中不能重复

4、初始化IoC容器(Spring核心容器/Spring容器),通过容器获取bean

public class app2 {
    public static void main(String[] args) {
        //3、获取IoC容器
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        //获取资源
        BookService bookService = (BookService) ctx.getBean("bookService");
		bookService.save();
    }
}

DI入门案例

DI入门案例思路分析

  1. 基于IoC管理bean
  2. service中使用new形式创建Dao对象是否保留?(否)
  3. Service中需要的Dao对象如何进入到Service中?(提供方法)
  4. Service与Dao间的关系如何描述?(配置)

案例实现

修改BookServiceImpl

public class BookServiceImpl implements BookService {
    //5、删除业务层逻辑中使用new的方式创建的dao对象
    //private BookDao bookDao = new BookDaoImpl();

    private BookDao bookDao;

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }
    //6、提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

Spring配置文件中添加server与dao的关系

<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>

    <bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
	<!--	配置service与dao的关系
       		property标签表示配置当前bean的属性
       		name属性表示配置哪一个具体的属性
       		ref属性表示参照哪一个bean-->
        <property name="bookDao" ref="bookDao"/>
    </bean>

app2运行结果

image-20220503145247632

如果注释掉property标签,则会抛出NullPointerException异常

image-20220503145340164

总结

1、删除使用new的形式创建对象的代码

public class BookServiceImpl implements BookService {
    //5、删除业务层逻辑中使用new的方式创建的dao对象
    //private BookDao bookDao = new BookDaoImpl();

    private BookDao bookDao;

    public void save() {
        bookDao.save();
    }
}

2、提供依赖对象对应的setter方法

public class BookServiceImpl implements BookService {

    private BookDao bookDao;
    
    public void save() {
        bookDao.save();
    }
    //6、提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

3、配置service与dao之间的关系

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd

    <bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>

    <bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>
image-20220503150031291

ref的bookDao指的是当前容器对应的bean的名称(也就是这里的id),name的bookDao是现在的属性的名称。

Bean基础配置

image-20220503150704843

Bean别名配置

image-20220503151701582

修改Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd ">

    <bean id="bookDao" name="dao" class="com.le1a.dao.impl.BookDaoImpl"/>
    <!-- 新增name属性-->

    <bean id="bookService" name="service service2" class="com.le1a.service.impl.BookServiceImpl">
        <!-- 新增name属性-->
        <property name="bookDao" ref="dao"/>
    </bean>
</beans>
image-20220503151318720

但是,当如果使用了未被定义的别名时,将会抛出异常,例如这里使用了service4,但是我们并没有定义这个别名

image-20220503151438236

Bean作用范围

image-20220503153307501
public class AppForScope {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao1 = (BookDao) ctx.getBean("bookDao");
        BookDao bookDao2 = (BookDao) ctx.getBean("bookDao");

        System.out.println(bookDao1);
        System.out.println(bookDao2);
    }
}
image-20220503152828979

通过运行结果可以发现,Spring给我们创建的是一个单例Bean,两个bookDao是同一个地址

如果我们想要创建非单例的bean呢?

通过配置来完成,在Spring的配置文件中的bookDao配置中,再添加一个属性scope,一共有两个属性,分别是singletonprototype,默认的是singleton,如果换成prototype,Spring创建的Bean就会是两个不同的地址

image-20220503153157301
image-20220503153213216
  • 为什么bean默认为单例呢?

    • 对于Spring来说,它帮我们管理的bean要放入IoC容器中,如果创建出的bean不是单例的,那么bean的数量将会是无数个,用一次造一个,用一次造一个,所以使用单例bean会提高容器的效率。
  • 适合交给容器进行管理的bean

    • 表现层对象
    • 业务层对象
    • 数据从对象
    • 工具对象
  • 不适合交给容器进行管理的对象

    • 封装实体的域对象

Bean实例化

构造方法(常用)

bean实例化

  • bean本质上就是对象,创建bean使用构造方法完成

配置文件

<!--方式一:使用构造方法实例化bean-->
<!--    <bean id="bookDao" name="dao" class="com.le1a.dao.impl.BookDaoImpl" scope="prototype"/>-->

AppForInstanceBook

public class AppForInstanceBook {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookDao bookDao =(BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}

BookDaoImpl

public class BookDaoImpl implements BookDao {
    public BookDaoImpl(){
        System.out.println("book dao constructor is running ...");
    }
    public void save(){
        System.out.println("book dao save ...");
    }
}
image-20220503155257068

说明了造对象都会调用无参构造方法来构造,无论构造方法是public还是private属性。

image-20220503155746206

静态工厂(了解)

配置文件

<!--    方式二:使用静态工厂实例化bean-->
<bean id="orderDao" class="com.le1a.factory.OrderDaoFactory" factory-method="getOrderDao"/>

静态工厂

public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl();
    }
}

AppForInstanceOrder

public class AppForInstanceOrder {
    public static void main(String[] args) {
//        通过静态工厂创建对象
//        OrderDao orderDao = OrderDaoFactory.getOrderDao();
//        orderDao.save();

        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        OrderDao orderDao = (OrderDao) ctx.getBean("orderDao");
        orderDao.save();
    }
}

实例工厂(了解)与FactoryBean(实用)

配置文件

<!--    方式三: 使用实例工厂实例化bean-->
    <bean id="userFactory" class="com.le1a.factory.UserDaoFactory"/>
    <bean id="userDao" factory-bean="userFactory" factory-method="getUserDao"/>

实例工厂

public class UserDaoFactory {
    public UserDao getUserDao(){
        return new UserDaoImpl();
    }
}

AppForInstanceUser

public class AppForInstanceUser {
    public static void main(String[] args) {
//        创建实例工厂对象
//        UserDaoFactory userDaoFactory = new UserDaoFactory();
//        通过实例工厂对象创造对象
//        UserDao userDao = userDaoFactory.getUserDao();
//        userDao.save();

        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        userDao.save();
    }
}
image-20220503165549334

这个方法名是不固定的每次都需要配置,所以改良一下,使用第四种方法。

UserDaoFactoryBean⭐⭐⭐⭐⭐

public class UserDaoFactoryBean implements FactoryBean<UserDao> {
    //代替原始实例工厂中创建对象的方法
    @Override
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    @Override
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

配置文件

<!--    方式四: 使用FactoryBean实例化bean-->
<bean id="userDao" class="com.le1a.factory.UserDaoFactoryBean"/>

AppForInstanceUser

public class AppForInstanceUser {
    public static void main(String[] args) {

        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserDao userDao = (UserDao) ctx.getBean("userDao");
        userDao.save();

    }
}

可以发现通过FactoryBean实例化的Bean也是单例的

image-20220503170203766

如果要实现非单例的话,在UserDaoFactoryBean中新增一个方法

@Override
public boolean isSingleton() {
    return false;//true为单例,false为非单例
}
image-20220503170624116
image-20220503170712187

Bean的生命周期

  • 生命周期: 从创建到消亡的完整过程
  • bean生命周期: bean从创建到销毁的整体过程
  • bean生命周期的控制: 在bean创建后到销毁前做一些事情

Bean的生命周期控制(一):使用配置

BookDaoImpl

public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
        System.out.println("book dao save ...");
    }
    //表示bean初始化对应的操作
    public void init(){
        System.out.println("init ...");
    }

    //表示bean销毁前对应的操作
    public void destory(){
        System.out.println("destory ...");
    }
}

配置文件

必须得配置了这两个方法,才会被识别为初始化方法和销毁方法

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl" init-method="init" destroy-method="destory"/>

    <bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
        <property name="bookDao" ref="bookDao"/>
    </bean>
</beans>

AppForLifeCycle

public class AppForLifeCycle {
    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}

BookServiceImpl

public class BookServiceImpl implements BookService {
    private BookDao bookDao;

    @Override
    public void save() {
        System.out.println("book service ...");
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

在bean中实现初始化和销毁的方法,并且在配置文件中配置,然后就可以实现bean生命周期的控制。

执行AppForLifeCycle发现初始化方法执行了,但销毁方法并没有被执行

image-20220504133859722

原因是因为现在这个AppForLifeCycle程序是运行在java虚拟机中的,虚拟机启动了,IoC容器加载配置也启动了,然后把Bean初始化了并且拿到了Bean,下一步程序运行完了,虚拟机就退出了,并没有给Bean销毁的机会。解决办法有如下两种:

Bean销毁方法(一):

使用close()方法在虚拟机退出之前把容器给关闭

image-20220504134543454

但是发现ApplicationContext接口并没有实现close()方法,在ClassPathXmlApplicationContext接口的父类AbstractApplicationContext.class中实现了close()方法,所以只需要把ApplicationContext改为ClassPathXmlApplicationContext就可以了

image-20220504135750757
image-20220504140232062

如果想要在程序中正常的关闭容器,使用ClassPathXmlApplicationContext类就可以关了

Bean销毁方法(二):

使用registerShutdownHook()设置关闭钩子,表示在关闭虚拟机之前,先把容器关掉!

image-20220504140738206

两种方式的区别:

  1. close()方法相对暴力,如果close()放在前面,则会导致异常;而registerShutdownHook()则可以放在任意位置
image-20220504140936367

Bean的生命周期控制(二): 接口控制

如果bean的初始化和销操作都需要在配置文件中声明对应的方法名的话,太过于复杂。所以来看看Spring的方法来初始化

BookServiceImpl

public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {
    private BookDao bookDao;

    @Override
    public void save() {
        System.out.println("book service ...");
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void destroy() throws Exception {//销毁方法
        System.out.println("service destroy");
    }

    @Override
    public void afterPropertiesSet() throws Exception {//在Bean初始化属性设置完之后,执行service的初始化方法
        System.out.println("service init");
    }
}
image-20220504142145264

Bean同样可以继承这两个接口,并实现初始化和销毁的方法,这样就可以不用在spring配置文件中添加init-method="init" destroy-method="destroy"

BookDaoImpl

public class BookDaoImpl implements BookDao , InitializingBean, DisposableBean {
    @Override
    public void save() {
        System.out.println("book dao save ...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("bean destroy ...");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("bean init ....");
    }

总结:

  • 初始化容器
    1. 创建对象(内存分配)
    2. 执行构造方法
    3. 执行属性注入(set操作)
    4. 执行bean初始化方法
  • 使用bean
    1. 执行业务操作
  • 关闭/销毁容器
    1. 执行bean销毁方法

依赖注入方式

  • 思考:向一个类中传递数据的方式有几种?
    1. 普通方法(set方法)
    2. 构造方法
  • 思考:依赖注入描述了容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?
    1. 引用类型
    2. 简单类型(基本数据类型和String)
  • 依赖注入方式
    • setter注入
      • 简单类型
      • 引用类型
    • 构造器注入
      • 简单类型
      • 引用类型

setter注入——引用类型

  • 在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService , InitializingBean, DisposableBean {
    private BookDao bookDao;
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
  • 配置中使用property标签ref属性注入引用类型对象
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>

<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
</bean>

如果有多个bean的时候,同样的配置就好了

AppForDISet

public class AppForDISet {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

        BookService bookService = (BookService) ctx.getBean("bookService");
        bookService.save();
    }
}

配置文件

<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>
<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>


<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
    <property name="userDao" ref="userDao"/>
</bean>

BookServiceImpl

public class BookServiceImpl implements BookService {
    private BookDao bookDao;
    private UserDao userDao;

    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }

    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }

    @Override
    public void save() {
        System.out.println("book service ...");
        bookDao.save();
        userDao.save();
    }

setter注入——简单类型

简单类型就直接在Bean里面配置set方法,并且在配置文件中配置bean的property标签即可

image-20220504151519821
image-20220504151609530

运行结果如图

image-20220504151719038
image-20220504151953623

构造器注入——引用类型

image-20220504164215387

构造器注入的话就把之前BookServiceImplset方法改为构造方法就好了

public BookServiceImpl(BookDao bookDao, UserDao userDao) {
        this.bookDao = bookDao;
        this.userDao = userDao;
    }
<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl"/>

<bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao"/>
    <constructor-arg name="userDao" ref="userDao"/>
</bean>

构造器注入——简单类型

image-20220504164426602

BookDaoImpl 在Bean中使用构造器

public class BookDaoImpl implements BookDao{
    private int connectionNum;
    private String databaseName;

    public BookDaoImpl(int connectionNum, String databaseName) {
        this.connectionNum = connectionNum;
        this.databaseName = databaseName;
    }

    public void save() {
        System.out.println("book dao save ..."+databaseName+","+connectionNum);
    }
}

配置文件中配置constructor-arg标签

<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
    <constructor-arg name="databaseName" value="mysql"/>
    <constructor-arg name="connectionNum" value="666"/>
</bean>
image-20220504162505178

高耦合解决办法

image-20220504164501005

这里会出现一个耦合度高的问题,因为配置文件中的name属性是指向的set方法/构造器的形参,如果形参发生改变,那么这里的name属性同样需要改变,比较麻烦。

<!--    标准书写 
    <bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
        <constructor-arg name="databaseName" value="mysql"/>
        <constructor-arg name="connectionNum" value="666"/>
    </bean>

    <bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>


    <bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
    -->

方法一

解决方案就是 不写name属性,写type类型,通过type类型来限制赋值,通过实验发现确实可以

<!--    解决形参名称问题,与形参名不耦合
    <bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
        <constructor-arg type="java.lang.String" value="666"/>
        <constructor-arg type="java.lang.String" value="mysql"/>
    </bean>

    <bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>


    <bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>
-->
image-20220504163445902

但是如果两个参数是同样的类型,就会按照顺序赋值,这显然不是我们想要的

image-20220504163752358

方法二

使用参数位置来解决参数匹配问题

<bean id="bookDao" class="com.le1a.dao.impl.BookDaoImpl">
        <constructor-arg index="0" value="666"/>
        <constructor-arg index="1" value="mysql"/>
    </bean>

    <bean id="userDao" class="com.le1a.dao.impl.UserDaoImpl"/>


    <bean id="bookService" class="com.le1a.service.impl.BookServiceImpl">
        <constructor-arg name="bookDao" ref="bookDao"/>
        <constructor-arg name="userDao" ref="userDao"/>
    </bean>

依赖注入方式选择

  1. 强制依赖使用构造器,使用setter注入有概率不进行注入导致null对象出现
  2. 可选依赖使用setter注入进行,灵活性强
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式对数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入

Spring自动装配